EDA#
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objs as go
from phik import phik_matrix
from numpy import log10, NaN
from seaborn import heatmap, set_theme
from json import load
from urllib.request import urlopen
from plotly.subplots import make_subplots
set_theme(style="ticks", context="talk", palette="tab10")
plt.rcParams.update({'font.size': 12})
Archivo GeoJSON para trazar los mapas más tarde
with urlopen('https://gist.githubusercontent.com/john-guerra/43c7656821069d00dcbc/raw/be6a6e239cd5b5b803c6e7c2ec405b793a9064dd/Colombia.geo.json') as response:
counties = load(response)
df = pd.read_parquet('data_cleaned.parquet')
df_factorize = df.apply(lambda x : pd.factorize(x)[0])
df_corr = df_factorize.phik_matrix(interval_cols=list(df.columns)).copy()
Como podemos ver en el mapa de calor, hay una relación entre las columnas de género y grupo_etario.
fig, ax = plt.subplots(figsize=(10,7))
heatmap(df_corr, annot=True, linewidths=.6)
plt.show()
Distribución De los Datos:
Distribución de datos factorizados: no hay una distribución común ni consistente
df_factorize.hist(figsize=(8, 10), column=['departamento', 'municipio', 'genero', 'grupo_etario','armas_medios'],
grid=True, bins=20)
plt.show()
Distribución de columnas de tipo datetime e integer: En la fecha, podemos ver cómo en 2020 hubo un gran aumento en los casos, hablaremos de esto más adelante en el análisis de series temporales.
En los casos, hay una gran concentración de 1 a 2 casos por fila.
df.hist(figsize=(13, 5), bins=50)
plt.show()
df.describe(exclude='datetime')
| departamento | municipio | armas_medios | genero | grupo_etario | cantidad | department | |
|---|---|---|---|---|---|---|---|
| count | 570467 | 570467 | 570467 | 570467 | 570467 | 570467.000000 | 54929 |
| unique | 32 | 1023 | 5 | 3 | 4 | NaN | 1 |
| top | CUNDINAMARCA | BOGOTÁ D.C. (CT) | ARMA BLANCA | FEMENINO | ADULTOS | NaN | SANTAFE DE BOGOTA D.C |
| freq | 100753 | 54929 | 323262 | 433910 | 499973 | NaN | 54929 |
| mean | NaN | NaN | NaN | NaN | NaN | 1.542531 | NaN |
| std | NaN | NaN | NaN | NaN | NaN | 1.644632 | NaN |
| min | NaN | NaN | NaN | NaN | NaN | 1.000000 | NaN |
| 25% | NaN | NaN | NaN | NaN | NaN | 1.000000 | NaN |
| 50% | NaN | NaN | NaN | NaN | NaN | 1.000000 | NaN |
| 75% | NaN | NaN | NaN | NaN | NaN | 1.000000 | NaN |
| max | NaN | NaN | NaN | NaN | NaN | 19.000000 | NaN |
El departamento con más casos y su distribución:
table_department = df.groupby('departamento')['cantidad'].sum().reset_index()
# If you need to rename the column, you can do so
table_department = table_department.rename(columns={'cantidad': 'total_cantidad'})
# Now you can sort the DataFrame
table_department_sorted = table_department.sort_values(by='total_cantidad', ascending=False)
print(table_department_sorted)
departamento total_cantidad
14 CUNDINAMARCA 198440
1 ANTIOQUIA 118702
29 VALLE DEL CAUCA 93680
26 SANTANDER 64965
6 BOYACA 40437
4 ATLANTICO 33812
20 META 30026
5 BOLIVAR 29965
28 TOLIMA 27614
17 HUILA 26379
21 NARIÑO 25714
22 NORTE DE SANTANDER 25472
25 RISARALDA 23592
10 CAUCA 23535
19 MAGDALENA 16211
13 CORDOBA 16199
27 SUCRE 14102
7 CALDAS 12641
11 CESAR 12354
9 CASANARE 8548
8 CAQUETA 6840
24 QUINDIO 6686
18 LA GUAJIRA 5870
2 ARAUCA 4893
23 PUTUMAYO 4128
12 CHOCO 2773
0 AMAZONAS 1714
3 ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA... 1620
16 GUAVIARE 1301
15 GUAINIA 665
30 VAUPES 566
31 VICHADA 519
locs = table_department['departamento']
for loc in counties['features']:
loc['id'] = loc['properties']['NOMBRE_DPT']
fig = make_subplots(
rows=1, cols=2,
subplot_titles=['Normal distribution', 'Logarithm 10'],
specs=[[{"type": "mapbox"}, {"type": "mapbox"}]]
)
fig.add_trace(
go.Choroplethmapbox(
geojson=counties,
locations=table_department['departamento'],
z=table_department['total_cantidad'],
colorbar_title='Casos',
colorscale='YlOrRd',
colorbar=dict(thickness=20, x=0.46),
marker=dict(opacity=0.75)
),
row=1, col=1
)
fig.add_trace(
go.Choroplethmapbox(
geojson=counties,
locations=table_department['departamento'],
z=log10(table_department['total_cantidad']),
colorbar_title='Case count (Log10)',
colorscale='YlOrRd',
colorbar=dict(thickness=20, x=1.02),
marker=dict(opacity=0.75)
),
row=1, col=2
)
fig.update_layout(
margin=dict(l=20, r=0, t=80, b=40),
title='Casos de Violencia doméstica distribuidos en Colombia',
mapbox1=dict(zoom=3.4, style='carto-positron', center={"lat": 4.570868, "lon": -74.2973328}),
mapbox2=dict(zoom=3.4, style='carto-positron', center={"lat": 4.570868, "lon": -74.2973328})
)
fig.write_html('plot.html')
from IPython.display import HTML
HTML(filename='plot.html')
Los casos en todo el país están concentrados en la parte central del país, como era de esperar debido a que una gran cantidad de población se encuentra en esta región.
El lado derecho del gráfico muestra una función logarítmica con base 10 aplicada a los valores del lado izquierdo del gráfico. Esto se hace únicamente con el propósito de mejorar la representación visual de cómo están distribuidos los casos en el país, y no refleja la distribución real de los datos.
¿Qué tipo de arma es la más común?
gun_table = pd.pivot_table(df, index = 'armas_medios', values = 'cantidad', columns = None, aggfunc='count',sort=True).reset_index()
gun_table
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\4238805791.py:1: FutureWarning:
The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
| armas_medios | cantidad | |
|---|---|---|
| 0 | ARMA BLANCA | 323262 |
| 1 | ARMA DE FUEGO | 2710 |
| 2 | ESCOPOLAMINA | 3854 |
| 3 | NO REPORTA | 75407 |
| 4 | SIN EMPLEO DE ARMAS | 165234 |
gun_graph = px.bar(gun_table, x='armas_medios', y='cantidad', text_auto='.2s', title='Tipo de arma usada')
gun_graph.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)
gun_graph.update_xaxes(tickvals=[0, 1, 2, 3, 4], ticktext=['ARMA BLANCA', 'ARMA DE FUEGO', 'ESCOPOLAMINA ', 'NO REPORTA', 'SIN EMPLEO DE ARMAS '])
gun_graph.write_html('gun_plot.html')
HTML(filename='gun_plot.html')
El tipo de arma más común utilizado en casos de violencia doméstica en Colombia es el arma blanca, seguida de sin empleo de armas. Es interesante ver cómo el arma blanca es mucho más utilizada que las otras, especialmente el arma de fuego y la escopolamina.
Distribución Por Género:
df.groupby('genero')['cantidad'].count()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\706662510.py:1: FutureWarning:
The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
genero
FEMENINO 433910
MASCULINO 136031
NO REPORTA 526
Name: cantidad, dtype: int64
Se puede observar que el género Femenimo es el que más casos reporta.
dfsex = pd.pivot_table(df, index = ('genero', 'armas_medios'), values = 'cantidad', columns = None, aggfunc='count', sort=True).reset_index()
dfsex
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2537065515.py:1: FutureWarning:
The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
| genero | armas_medios | cantidad | |
|---|---|---|---|
| 0 | FEMENINO | ARMA BLANCA | 248238 |
| 1 | FEMENINO | ARMA DE FUEGO | 2276 |
| 2 | FEMENINO | ESCOPOLAMINA | 2975 |
| 3 | FEMENINO | NO REPORTA | 56193 |
| 4 | FEMENINO | SIN EMPLEO DE ARMAS | 124228 |
| 5 | MASCULINO | ARMA BLANCA | 74870 |
| 6 | MASCULINO | ARMA DE FUEGO | 434 |
| 7 | MASCULINO | ESCOPOLAMINA | 877 |
| 8 | MASCULINO | NO REPORTA | 19106 |
| 9 | MASCULINO | SIN EMPLEO DE ARMAS | 40744 |
| 10 | NO REPORTA | ARMA BLANCA | 154 |
| 11 | NO REPORTA | ARMA DE FUEGO | 0 |
| 12 | NO REPORTA | ESCOPOLAMINA | 2 |
| 13 | NO REPORTA | NO REPORTA | 108 |
| 14 | NO REPORTA | SIN EMPLEO DE ARMAS | 262 |
dfsex['genero'].replace({'NO REPORTA': NaN}, inplace=True)
dfsex = dfsex[~dfsex['genero'].isnull()]
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\316684948.py:1: FutureWarning:
A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\316684948.py:1: FutureWarning:
The behavior of Series.replace (and DataFrame.replace) with CategoricalDtype is deprecated. In a future version, replace will only be used for cases that preserve the categories. To change the categories, use ser.cat.rename_categories instead.
Gráfico que compara cuántas víctimas hay por género, en términos del tipo de arma que se utilizó.
fig_sex = px.bar(dfsex, x='genero', y='cantidad', color='armas_medios', barmode='group', text_auto='.2s',
title="Género de las víctimas y el número de casos que involucran cada tipo de arma",
labels={'Cantidad':'Casos', 'genero':'Género y tipo de arma'}, height=400)
fig_sex.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)
fig_sex.write_html('sex_plot.html')
HTML(filename='sex_plot.html')
Relación entre género y Grupo Etario:
df.groupby(['genero', 'grupo_etario'])['cantidad'].count()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\758330160.py:1: FutureWarning:
The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
genero grupo_etario
FEMENINO ADOLESCENTES 24672
ADULTOS 392729
MENORES 16508
NO REPORTA 1
MASCULINO ADOLESCENTES 10879
ADULTOS 107226
MENORES 17926
NO REPORTA 0
NO REPORTA ADOLESCENTES 0
ADULTOS 18
MENORES 2
NO REPORTA 506
Name: cantidad, dtype: int64
dfsex_age = pd.pivot_table(df, index = ('genero', 'grupo_etario'), values = 'cantidad', columns = None, aggfunc='count', sort=True).reset_index()
dfsex_age
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\1976776229.py:1: FutureWarning:
The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
| genero | grupo_etario | cantidad | |
|---|---|---|---|
| 0 | FEMENINO | ADOLESCENTES | 24672 |
| 1 | FEMENINO | ADULTOS | 392729 |
| 2 | FEMENINO | MENORES | 16508 |
| 3 | FEMENINO | NO REPORTA | 1 |
| 4 | MASCULINO | ADOLESCENTES | 10879 |
| 5 | MASCULINO | ADULTOS | 107226 |
| 6 | MASCULINO | MENORES | 17926 |
| 7 | MASCULINO | NO REPORTA | 0 |
| 8 | NO REPORTA | ADOLESCENTES | 0 |
| 9 | NO REPORTA | ADULTOS | 18 |
| 10 | NO REPORTA | MENORES | 2 |
| 11 | NO REPORTA | NO REPORTA | 506 |
Como se observa aquí, la mayoría de los individuos afectados son mujeres adultas, mientras que hay poca variación en el número de menores afectados entre hombres y mujeres.
fig_sex_age = px.bar(dfsex_age, x='genero', y='cantidad', color='grupo_etario', barmode='group', text_auto='.2s',
title="Género de las victimas y número de casos según el tipo de arma",
labels={'cantidad':'Casos', 'genero':'Género y Tipo de arma'}, height=400)
fig_sex_age.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)
fig_sex_age.write_html('sex_age_plot.html')
HTML(filename='sex_age_plot.html')
Análisis de escopolamina:
df.head()
| departamento | municipio | armas_medios | fecha_hecho | genero | grupo_etario | cantidad | department | |
|---|---|---|---|---|---|---|---|---|
| 0 | ATLANTICO | BARRANQUILLA (CT) | ARMA BLANCA | 2010-01-01 | MASCULINO | ADULTOS | 1 | None |
| 1 | BOYACA | DUITAMA | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | None |
| 2 | CAQUETA | PUERTO RICO | ARMA BLANCA | 2010-01-01 | MASCULINO | ADULTOS | 1 | None |
| 3 | CASANARE | MANÍ | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | None |
| 4 | CUNDINAMARCA | BOGOTÁ D.C. (CT) | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | SANTAFE DE BOGOTA D.C |
dfsco = df.query('armas_medios == "ESCOPOLAMINA"')
dfsco
| departamento | municipio | armas_medios | fecha_hecho | genero | grupo_etario | cantidad | department | |
|---|---|---|---|---|---|---|---|---|
| 50040 | CUNDINAMARCA | BOGOTÁ D.C. (CT) | ESCOPOLAMINA | 2012-10-22 | FEMENINO | ADULTOS | 1 | SANTAFE DE BOGOTA D.C |
| 50304 | VALLE DEL CAUCA | CALI (CT) | ESCOPOLAMINA | 2012-10-28 | FEMENINO | ADULTOS | 1 | None |
| 75086 | CORDOBA | MONTERÍA (CT) | ESCOPOLAMINA | 2014-01-01 | FEMENINO | ADULTOS | 1 | None |
| 75087 | CUNDINAMARCA | BOGOTÁ D.C. (CT) | ESCOPOLAMINA | 2014-01-01 | FEMENINO | ADULTOS | 6 | SANTAFE DE BOGOTA D.C |
| 75088 | NORTE DE SANTANDER | CÚCUTA (CT) | ESCOPOLAMINA | 2014-01-01 | FEMENINO | ADULTOS | 2 | None |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 253836 | BOLIVAR | MAGANGUÉ | ESCOPOLAMINA | 2018-01-28 | FEMENINO | ADULTOS | 1 | None |
| 253837 | CUNDINAMARCA | SOACHA | ESCOPOLAMINA | 2018-01-28 | FEMENINO | ADULTOS | 2 | None |
| 253838 | QUINDIO | ARMENIA (CT) | ESCOPOLAMINA | 2018-01-28 | FEMENINO | ADULTOS | 1 | None |
| 253839 | TOLIMA | IBAGUÉ (CT) | ESCOPOLAMINA | 2018-01-28 | FEMENINO | ADULTOS | 1 | None |
| 254006 | VALLE DEL CAUCA | CALI (CT) | ESCOPOLAMINA | 2018-01-29 | FEMENINO | ADULTOS | 1 | None |
3854 rows × 8 columns
dfdsco = pd.pivot_table(dfsco, index = 'departamento', values = 'cantidad', columns = None, aggfunc='count').reset_index()
dfdsco['departamento'].replace({'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA':'SAN ANDRES'}, inplace=True)
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\223915053.py:2: FutureWarning:
A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.
Es cierto que el uso de escopolamina está concentrado principalmente en la región central del país, con una presencia significativa observada en Bogotá, la capital de Colombia. Esta ciudad también tiene el mayor número de casos que involucran el uso de escopolamina en incidentes de violencia doméstica, en comparación con otras regiones.
fig = px.bar(dfdsco, x='cantidad', y='departamento', text_auto='.2s',
title="Número de informes de violencia familiar con escopolamina por departamento",
labels={'departamento':'Departamento', 'cantidad':'Casos'}, orientation='h')
fig.update_traces(textfont_size=12, textangle=0, textposition="outside",cliponaxis=False)
fig.update_layout(width=1200, height=800)
fig.write_html('dsco_plot.html')
HTML(filename='dsco_plot.html')
Mapa de casos de escopolamina distribuidos en el país, con la función logaritmo base 10 aplicada para visualizar mejor su distribución.
locs = dfdsco['departamento']
for loc in counties['features']:
loc['id'] = loc['properties']['NOMBRE_DPT']
fig = go.Figure(go.Choroplethmapbox(
geojson=counties,
locations=locs,
z=log10(dfdsco['cantidad']),
colorscale='YlOrRd',
colorbar_title="Número"
))
fig.update_layout(
mapbox_style="carto-positron",
mapbox_zoom=3.3,
mapbox_center={"lat": 4.570868, "lon": -74.2973328},
title='Casos de escopolamina distribuidos en el país Log10'
)
fig.write_html('dsco_map_plot.html')
HTML(filename='dsco_map_plot.html')
Análisis de Series de Tiempo:
df['day'] = df['fecha_hecho'].dt.day_name()
df['month'] = df['fecha_hecho'].dt.month_name()
df.head()
| departamento | municipio | armas_medios | fecha_hecho | genero | grupo_etario | cantidad | department | day | month | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ATLANTICO | BARRANQUILLA (CT) | ARMA BLANCA | 2010-01-01 | MASCULINO | ADULTOS | 1 | None | Friday | January |
| 1 | BOYACA | DUITAMA | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | None | Friday | January |
| 2 | CAQUETA | PUERTO RICO | ARMA BLANCA | 2010-01-01 | MASCULINO | ADULTOS | 1 | None | Friday | January |
| 3 | CASANARE | MANÍ | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | None | Friday | January |
| 4 | CUNDINAMARCA | BOGOTÁ D.C. (CT) | ARMA BLANCA | 2010-01-01 | FEMENINO | ADULTOS | 1 | SANTAFE DE BOGOTA D.C | Friday | January |
new_order_month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
new_order_day = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df_day = df.groupby('day')['cantidad'].sum().reindex(new_order_day, axis=0).reset_index()
df_month = df.groupby('month')['cantidad'].sum().reindex(new_order_month, axis=0).reset_index()
fig, (ax1, ax2) = plt.subplots(2, figsize=(10,16))
for i, (month, cases) in enumerate(zip(df_month['month'], df_month['cantidad'])):
ax1.bar(month, cases)
ax1.annotate(str(cases), xy=(month, cases), ha='center', va='bottom')
ax1.set_xlabel('Meses')
ax1.set_ylabel('Casos')
ax1.set_title('Casos por mes')
ax1.set_xticklabels(df_month['month'], rotation=25)
ax1.set_xticks(range(len(df_month['month'])))
for i, (day, cases) in enumerate(zip(df_day['day'], df_day['cantidad'])):
ax2.bar(day, cases)
ax2.annotate(str(cases), xy=(day, cases), ha='center', va='bottom')
ax2.set_xlabel('Días de la semana')
ax2.set_ylabel('Casos')
ax2.set_title('Casos por día de la semana')
ax2.set_xticklabels(df_day['day'], rotation=25)
ax2.set_xticks(range(len(df_day['day'])))
plt.tight_layout()
plt.savefig('total_cases_month_and_day_with_labels.jpg')
plt.show()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2426525920.py:10: UserWarning:
set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2426525920.py:20: UserWarning:
set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
Podemos observar cómo los casos por mes tienen una tendencia estable, excepto en diciembre, este mes tiene una disminución abrupta que puede deberse a las festividades celebradas durante esta época del año.
Asimismo, los domingos, que es el día de descanso en Colombia, es el día con el mayor número de casos de la semana.
df_date1 = df.groupby('fecha_hecho')['cantidad'].sum().reset_index()
df_date2 = df.groupby(df.fecha_hecho.dt.year)['cantidad'].sum().reset_index()
df_date2 = df_date2.rename({'fecha_hecho':'year'}, axis=1)
fig, (ax1, ax2, ax3) = plt.subplots(3, figsize=(20,16))
ax1.plot(df_date1['fecha_hecho'], df_date1['cantidad'], marker='o', color='b')
ax1.set_xlabel('Fecha')
ax1.set_ylabel('Casos')
ax1.set_title('Timeline de casos diarios')
ax1.grid(True)
ax2.bar(df_date2['year'], df_date2['cantidad'], color='g')
ax2.set_xlabel('Años')
ax2.set_ylabel('Casos')
ax2.set_title('Total de casos agrupados por año')
ax2.grid(True)
df_date1_filtered = df_date1[df_date1['fecha_hecho'] > '2019-01-01']
ax3.plot(df_date1_filtered['fecha_hecho'], df_date1_filtered['cantidad'], marker='s', color='r')
ax3.set_xlabel('Fecha')
ax3.set_ylabel('Casos')
ax3.set_title('Distribución de casos diarios desde 2019 hasta 2022')
ax3.grid(True)
plt.tight_layout()
plt.savefig('timeline_graph.jpg')
plt.show()
Se puede concluir a partir de los gráficos anteriores que cada fin de diciembre y principio de enero, durante las vacaciones en Colombia, hay un aumento en los casos reportados. Esto ocurre todos los años en todo el país.